package adc.app.impl.jpa;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.From;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import adc.app.spec.IBo;
import adc.app.spec.IDao;

import com.google.common.collect.Lists;

public abstract class JpaDao<T extends IBo> implements IDao<T> {


    @PersistenceContext
    private EntityManager entityManager;

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public <X extends T> X byId(Class<X> boClass, Long id) {
        if (id != null) {
            return entityManager.getReference(implClass(boClass), id);
        }
        return null;
    }

    @Override
    public <X extends T> X create(Class<X> boClass) {
        final Class<? extends X> implClass = implClass(boClass);
        try {
            return implClass.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalStateException("Failed to create business object of type " + boClass.getName(), e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Failed to create business object of type " + boClass.getName(), e);
        }
    }

    @Override
    public <X extends T> void delete(X bo) {
        entityManager.remove(bo);

    }

    @Override
    public <X extends T> void save(X bo) {
        entityManager.persist(bo);
    }
    @Override
    public <X extends T> List<X> list(Class<X> boClass, Map<String, Object> filter, Integer firstIndex,
            Integer maxResultCount, Map<String, Boolean> order) {
        final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        Class<X> implClass = implClass(boClass);
        CriteriaQuery<X> q = cb.createQuery(implClass);
        Root<? extends X> from = q.from(implClass);
        if (filter != null && !filter.isEmpty()) {
            for (Entry<String, Object> filterEntry : filter.entrySet()) {
                final Predicate filterPred;
                if (filterEntry.getValue() == null) {
                    filterPred = cb.isNull(from.get(filterEntry.getKey()));
                } else {
                    filterPred = cb.equal(from.get(filterEntry.getKey()), filterEntry.getValue());
                }
                q.where(filterPred);
            }
        }
        if (order != null && !order.isEmpty()) {
            List<Order> orders = Lists.newArrayList();
            for (Entry<String, Boolean> orderEntry : order.entrySet()) {
                final String[] attrs = orderEntry.getKey().split("\\.");
                Path<?> sortExpr;
                if (attrs.length > 0) {
                    sortExpr = from;
                    for (int i = 0; i < attrs.length - 1; i++) {
                        sortExpr = ((From<?,?>) sortExpr).join(attrs[i], JoinType.LEFT);
                    }
                    sortExpr = sortExpr.get(attrs[attrs.length - 1]);
                } else {
                    sortExpr = from.get(orderEntry.getKey());
                }
                orders.add(Boolean.TRUE.equals(orderEntry.getValue()) ? cb.asc(sortExpr) : cb.desc(sortExpr));
                
            }
            q.orderBy(orders);
        }
        TypedQuery<X> tq = entityManager.createQuery(q);
        if (firstIndex != null && firstIndex.compareTo(Integer.valueOf(0)) > 0) {
            tq.setFirstResult(firstIndex.intValue());
        }
        if (maxResultCount != null && maxResultCount.compareTo(Integer.valueOf(0)) > 0) {
            tq.setMaxResults(maxResultCount.intValue());
        }
        return tq.getResultList();
    }

    

    
    @Override
    public <X extends T> Integer count(Class<X> boClass, Map<String, Object> filter) {
        final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> q = entityManager.getCriteriaBuilder().createQuery(Long.class);
        Root<? extends X> from = q.from(implClass(boClass));
        q.select(cb.count(from));
        if (filter != null && !filter.isEmpty()) {
            for (Entry<String, Object> filterEntry : filter.entrySet()) {
                final Predicate filterPred;
                if (filterEntry.getValue() == null) {
                    filterPred = cb.isNull(from.get(filterEntry.getKey()));
                } else {
                    filterPred = cb.equal(from.get(filterEntry.getKey()), filterEntry.getValue());
                }
                q.where(filterPred);
            }
        }
        TypedQuery<Long> tq = entityManager.createQuery(q);
        return tq.getSingleResult().intValue();
    }

    protected abstract <X extends IBo, R extends X> Class<R> implClass(Class<X> specClass);
}
